const setup = require('./support/setup');
const NODE = setup.NODE;
const uri = setup.uri;

const assert = require('assert');
const request = require('../');

describe('request', function(){
  this.timeout(20000);

  describe('res.statusCode', () => {
    it('should set statusCode', done => {
      request
      .get(`${uri}/login`, (err, res) => {
        try {
        assert.strictEqual(res.statusCode, 200);
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('should allow the send shorthand', () => {
    it('with callback in the method call', done => {
      request
      .get(`${uri}/login`, (err, res) => {
          assert.equal(res.status, 200);
          done();
      });
    })

    it('with data in the method call', done => {
      request
      .post(`${uri}/echo`, { foo: 'bar' })
      .end((err, res) => {
        assert.equal('{"foo":"bar"}', res.text);
        done();
      });
    })

    it('with callback and data in the method call', done => {
      request
      .post(`${uri}/echo`, { foo: 'bar' }, (err, res) => {
        assert.equal('{"foo":"bar"}', res.text);
        done();
      });
    })
  })

  describe('with a callback', () => {
    it('should invoke .end()', done => {
      request
      .get(`${uri}/login`, (err, res) => {
        try {
        assert.equal(res.status, 200);
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('.end()', () => {
    it('should issue a request', done => {
      request
      .get(`${uri}/login`)
      .end((err, res) => {
        try {
        assert.equal(res.status, 200);
        done();
        } catch(e) { done(e); }
      });
    })

    it('is optional with a promise', () => {
      if ('undefined' === typeof Promise) {
        return;
      }

      return request.get(`${uri}/login`)
      .then(res => res.status)
      .then()
      .then(status => {
          assert.equal(200, status, "Real promises pass results through");
      });
    });

    it('called only once with a promise', () => {
      if ('undefined' === typeof Promise) {
        return;
      }

      const req = request.get(`${uri}/unique`);

      return Promise.all([req, req, req])
      .then(results => {
        results.forEach(item => {
          assert.equal(item.body, results[0].body, "It should keep returning the same result after being called once");
        });
      });
    });
  })


  describe('res.error', () => {
    it('ok', done => {
      let calledErrorEvent = false;
      let calledOKHandler = false;
      request
      .get(`${uri}/error`)
      .ok(res => {
        assert.strictEqual(500, res.status);
        calledOKHandler = true;
        return true;
      })
      .on('error', err => {
        calledErrorEvent = true;
      })
      .end((err, res) => {
        try{
          assert.ifError(err);
          assert.strictEqual(res.status, 500);
          assert(!calledErrorEvent);
          assert(calledOKHandler);
          done();
        } catch(e) { done(e); }
      });
    });

    it('should should be an Error object', done => {
      let calledErrorEvent = false;
      request
      .get(`${uri}/error`)
      .on('error', err => {
        assert.strictEqual(err.status, 500);
        calledErrorEvent = true;
      })
      .end((err, res) => {
        try {
        if (NODE) {
          res.error.message.should.equal('cannot GET /error (500)');
        }
        else {
          res.error.message.should.equal(`cannot GET ${uri}/error (500)`);
        }
        assert.strictEqual(res.error.status, 500);
        assert(err, 'should have an error for 500');
        assert.equal(err.message, 'Internal Server Error');
        assert(calledErrorEvent);
        done();
        } catch(e) { done(e); }
      });
    })

    it('with .then() promise', () => {
      if ('undefined' === typeof Promise) {
        return;
      }

      return request
      .get(`${uri}/error`)
      .then(() => {
        assert.fail();
      }, err => {
        assert.equal(err.message, 'Internal Server Error');
      });
    })

    it('with .ok() returning false', () => {
      if ('undefined' === typeof Promise) {
        return;
      }

      return request
      .get(`${uri}/echo`)
      .ok(() => false)
      .then(() => {
        assert.fail();
      }, err => {
        assert.equal(200, err.response.status);
        assert.equal(err.message, 'OK');
      });
    })

    it('with .ok() throwing an Error', () => {
      if ('undefined' === typeof Promise) {
        return;
      }

      return request
      .get(`${uri}/echo`)
      .ok(() => {throw new Error('boom');})
      .then(() => {
        assert.fail();
      }, err => {
        assert.equal(200, err.response.status);
        assert.equal(err.message, 'boom');
      });
    })
  })

  describe('res.header', () => {
    it('should be an object', done => {
      request
      .get(`${uri}/login`)
      .end((err, res) => {
        try {
        assert.equal('Express', res.header['x-powered-by']);
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('set headers', () => {
    before(() => {
      Object.prototype.invalid = 'invalid';
    });

    after(() => {
      delete Object.prototype.invalid;
    });

    it('should only set headers for ownProperties of header', done => {
      try {
        request
          .get(`${uri}/echo-headers`)
          .set('valid', 'ok')
          .end((err, res) => {
            if (!err && res.body && res.body.valid && !res.body.hasOwnProperty('invalid')) {
              return done();
            }
            done(err || Error("fail"));
          });
      } catch (e) {
        done(e)
      }
    });
  });

  describe('res.charset', () => {
    it('should be set when present', done => {
      request
      .get(`${uri}/login`)
      .end((err, res) => {
        try {
        res.charset.should.equal('utf-8');
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('res.statusType', () => {
    it('should provide the first digit', done => {
      request
      .get(`${uri}/login`)
      .end((err, res) => {
        try {
        assert(!err, 'should not have an error for success responses');
        assert.equal(200, res.status);
        assert.equal(2, res.statusType);
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('res.type', () => {
    it('should provide the mime-type void of params', done => {
      request
      .get(`${uri}/login`)
      .end((err, res) => {
        try {
        res.type.should.equal('text/html');
        res.charset.should.equal('utf-8');
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('req.set(field, val)', () => {
    it('should set the header field', done => {
      request
      .post(`${uri}/echo`)
      .set('X-Foo', 'bar')
      .set('X-Bar', 'baz')
      .end((err, res) => {
        try {
        assert.equal('bar', res.header['x-foo']);
        assert.equal('baz', res.header['x-bar']);
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('req.set(obj)', () => {
    it('should set the header fields', done => {
      request
      .post(`${uri}/echo`)
      .set({ 'X-Foo': 'bar', 'X-Bar': 'baz' })
      .end((err, res) => {
        try {
        assert.equal('bar', res.header['x-foo']);
        assert.equal('baz', res.header['x-bar']);
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('req.type(str)', () => {
    it('should set the Content-Type', done => {
      request
      .post(`${uri}/echo`)
      .type('text/x-foo')
      .end((err, res) => {
        try {
        res.header['content-type'].should.equal('text/x-foo');
        done();
        } catch(e) { done(e); }
      });
    })

    it('should map "json"', done => {
      request
      .post(`${uri}/echo`)
      .type('json')
      .send('{"a": 1}')
      .end((err, res) => {
        try {
        res.should.be.json();
        done();
        } catch(e) { done(e); }
      });
    })

    it('should map "html"', done => {
      request
      .post(`${uri}/echo`)
      .type('html')
      .end((err, res) => {
        try {
        res.header['content-type'].should.equal('text/html');
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('req.accept(str)', () => {
    it('should set Accept', done => {
      request
      .get(`${uri}/echo`)
      .accept('text/x-foo')
      .end((err, res) => {
        try {
         res.header['accept'].should.equal('text/x-foo');
         done();
        } catch(e) { done(e); }
      });
    })

    it('should map "json"', done => {
      request
      .get(`${uri}/echo`)
      .accept('json')
      .end((err, res) => {
        try {
        res.header['accept'].should.equal('application/json');
        done();
        } catch(e) { done(e); }
      });
    })

    it('should map "xml"', done => {
      request
      .get(`${uri}/echo`)
      .accept('xml')
      .end((err, res) => {
        try {
          // Mime module keeps changing this :(
          assert(res.header['accept'] == "application/xml" || res.header['accept'] == "text/xml");
          done();
        } catch(e) { done(e); }
      });
    })

    it('should map "html"', done => {
      request
      .get(`${uri}/echo`)
      .accept('html')
      .end((err, res) => {
        try {
        res.header['accept'].should.equal('text/html');
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('req.send(str)', () => {
    it('should write the string', done => {
      request
      .post(`${uri}/echo`)
      .type('json')
      .send('{"name":"tobi"}')
      .end((err, res) => {
        try {
        res.text.should.equal('{"name":"tobi"}');
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('req.send(Object)', () => {
    it('should default to json', done => {
      request
      .post(`${uri}/echo`)
      .send({ name: 'tobi' })
      .end((err, res) => {
        try {
        res.should.be.json();
        res.text.should.equal('{"name":"tobi"}');
        done();
        } catch(e) { done(e); }
      });
    })

    describe('when called several times', () => {
      it('should merge the objects', done => {
        request
        .post(`${uri}/echo`)
        .send({ name: 'tobi' })
        .send({ age: 1 })
        .end((err, res) => {
            try {
          res.should.be.json();
          if (NODE) {
            res.buffered.should.be.true();
          }
          res.text.should.equal('{"name":"tobi","age":1}');
          done();
          } catch(e) { done(e); }
      });
      })
    })
  })

  describe('.end(fn)', () => {
    it('should check arity', done => {
      request
      .post(`${uri}/echo`)
      .send({ name: 'tobi' })
      .end((err, res) => {
        try {
        assert.equal(null, err);
        res.text.should.equal('{"name":"tobi"}');
        done();
        } catch(e) { done(e); }
      });
    })

    it('should emit request', done => {
      const req = request.post(`${uri}/echo`);
      req.on('request', request => {
        assert.equal(req, request);
        done();
      });
      req.end();
    })

    it('should emit response', done => {
      request
      .post(`${uri}/echo`)
      .send({ name: 'tobi' })
      .on('response', res => {
        res.text.should.equal('{"name":"tobi"}');
        done();
      })
      .end();
    })
  })

  describe('.then(fulfill, reject)', () => {
    it('should support successful fulfills with .then(fulfill)', done => {
      if ('undefined' === typeof Promise) {
        return done();
      }

      request
      .post(`${uri}/echo`)
      .send({ name: 'tobi' })
      .then(res => {
        res.text.should.equal('{"name":"tobi"}');
        done();
      })
    })

    it('should reject an error with .then(null, reject)', done => {
      if ('undefined' === typeof Promise) {
        return done();
      }

      request
      .get(`${uri}/error`)
      .then(null, err => {
        assert.equal(err.status, 500);
        assert.equal(err.response.text, 'boom');
        done();
      })
    })
  })

  describe('.catch(reject)', () => {
    it('should reject an error with .catch(reject)', done => {
      if ('undefined' === typeof Promise) {
        return done();
      }

      request
      .get(`${uri}/error`)
      .catch(err => {
        assert.equal(err.status, 500);
        assert.equal(err.response.text, 'boom');
        done();
      })
    })
  })

  describe('.abort()', () => {
    it('should abort the request', done => {
      const req = request
      .get(`${uri}/delay/3000`)
      .end((err, res) => {
        try {
        assert(false, 'should not complete the request');
        } catch(e) { done(e); }
      });

      req.on('error', error => {
        done(error);
      });
      req.on('abort', done);

      setTimeout(() => {
        req.abort();
      }, 500);
    })

    it('should allow chaining .abort() several times', done => {
      const req = request
      .get(`${uri}/delay/3000`)
      .end((err, res) => {
        try {
        assert(false, 'should not complete the request');
        } catch(e) { done(e); }
      });

      // This also verifies only a single 'done' event is emitted
      req.on('abort', done);

      setTimeout(() => {
        req.abort().abort().abort();
      }, 1000);
    })

    it('should not allow abort then end', done => {
      request
      .get(`${uri}/delay/3000`)
      .abort()
      .end((err, res) => {
        done(err ? undefined : Error("Expected abort error"));
      });
    })
  })

  describe('req.toJSON()', () => {
    it('should describe the request', done => {
      const req = request
      .post(`${uri}/echo`)
      .send({ foo: 'baz' })
      .end((err, res) => {
        try {
        const json = req.toJSON();
        assert.equal('POST', json.method);
        assert(/\/echo$/.test(json.url));
        assert.equal('baz', json.data.foo);
        done();
        } catch(e) { done(e); }
      });
    })
  })

  describe('req.options()', () => {
    it('should allow request body', done => {
      request.options(`${uri}/options/echo/body`)
      .send({ foo: 'baz' })
      .end((err, res) => {
        try {
        assert.equal(err, null);
        assert.strictEqual(res.body.foo, 'baz');
        done();
        } catch(e) { done(e); }
      });
    });
  });

  describe('req.sortQuery()', () => {
    it('nop with no querystring', done => {
      request
      .get(`${uri}/url`)
      .sortQuery()
      .end((err, res) => {
        try {
        assert.equal(res.text, '/url')
        done();
        } catch(e) { done(e); }
      });
    });

    it('should sort the request querystring', done => {
      request
      .get(`${uri}/url`)
      .query('search=Manny')
      .query('order=desc')
      .sortQuery()
      .end((err, res) => {
        try {
        assert.equal(res.text, '/url?order=desc&search=Manny')
        done();
        } catch(e) { done(e); }
      });
    });

    it('should allow disabling sorting', done => {
      request
      .get(`${uri}/url`)
      .query('search=Manny')
      .query('order=desc')
      .sortQuery() // take default of true
      .sortQuery(false) // override it in later call
      .end((err, res) => {
        try {
        assert.equal(res.text, '/url?search=Manny&order=desc')
        done();
        } catch(e) { done(e); }
      });
    });

    it('should sort the request querystring using customized function', done => {
      request
      .get(`${uri}/url`)
      .query('name=Nick')
      .query('search=Manny')
      .query('order=desc')
      .sortQuery((a, b) => a.length - b.length)
      .end((err, res) => {
        try {
        assert.equal(res.text, '/url?name=Nick&order=desc&search=Manny')
        done();
        } catch(e) { done(e); }
      });
    });
  })
})
